﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.Drawing;
using GenericTypeTea.Properties;
using System.IO;
using mshtml;

namespace GenericTypeTea
{
    public partial class GWinMap : WebBrowser
    {


#region Constructors


        public GWinMap()
        {

            this.ScrollBarsEnabled = false;
            this.TemporaryFolder = Application.StartupPath;
            this.WebBrowserShortcutsEnabled = false;

            this.DocumentCompleted+=new WebBrowserDocumentCompletedEventHandler(GWinMap_DocumentCompleted);
            
        }


#endregion


#region Configuration


        /// <summary>
        /// Initialises the control.
        /// </summary>
        /// <param name="key">The licence key registered for the Google Maps API.</param>
        public void Initialise(string key)
        {
            this.Initialise(key, false);
        }

        /// <summary>
        /// Initialises the control.
        /// </summary>
        /// <param name="key">The licence key registered for the Google Maps API.</param>
        /// <param name="sensor">Whether to enable the sensor property.</param>
        public void Initialise(string key, bool sensor)
        {
            _sensor = sensor;
            _key = key;
            _isInitialised = true;
        }

        /// <summary>
        /// Generates the map with the map type 'G_NORMAL_MAP'.
        /// </summary>
        public void Generate()
        {
            this.Generate(GWinMapTypes.G_NORMAL_MAP);
        }

        /// <summary>
        /// Generates the map with the specified map type.
        /// </summary>
        public void Generate(GWinMapTypes mapType)
        {

            if (this.IsInitialised)
            {

                // Generate the initial HTML
                string contents = Settings.Default.Template;
                contents = contents.Replace(GWinMapConstants.TEMPLATE_CANVAS_HEIGHT, this.Height.ToString() + GWinMapConstants.UNIT_PIXEL);
                contents = contents.Replace(GWinMapConstants.TEMPLATE_CANVAS_WIDTH, this.Width.ToString() + GWinMapConstants.UNIT_PIXEL);
                contents = contents.Replace(GWinMapConstants.TEMPLATE_KEY, this.Key);
                contents = contents.Replace(GWinMapConstants.TEMPLATE_SENSOR, this.Sensor.ToString().ToLower());
                contents = contents.Replace(GWinMapConstants.TEMPLATE_INITIALISE_FUNCTIONS, this.InitialisationFunctions.ToString());

                // Create the initial HTML file in the temporary directory
                _generatedFilePath = Path.Combine(this.TemporaryFolder, Guid.NewGuid().ToString() + ".html");
                File.WriteAllText(_generatedFilePath, contents);

                // Navigate to the file
                this.Navigate(_generatedFilePath);
                _isGenerated = true;

            }
            else
            {
                throw new GWinMapException(GWinMapErrors.CONTROL_NOT_INITIALISED);
            }

        }
        

#endregion


#region Properties


        /// <summary>
        /// Gets the path to the temporary generated file.
        /// </summary>
        protected string GeneratedFilePath
        {
            get
            {
                return _generatedFilePath;
            }
        }
        private string _generatedFilePath;
        
        /// <summary>
        /// Gets whether the control is initialised.
        /// </summary>
        public bool IsInitialised
        {
            get
            {
                return _isInitialised;
            }
        }
        private bool _isInitialised;

        /// <summary>
        /// Gets whether the control's content has been generated.
        /// </summary>
        public bool IsGenerated
        {
            get
            {
                return _isGenerated;
            }
        }
        protected bool _isGenerated;

        /// <summary>
        /// Gets whether the document is ready.
        /// </summary>
        public bool IsReady
        {
            get
            {
                return _isReady;
            }
        }
        private bool _isReady;

        /// <summary>
        /// Gets whether a center point has been set.
        /// </summary>
        public bool IsCenterSet
        {
            get
            {
                return _isCenterSet;
            }
        }
        private bool _isCenterSet;

        /// <summary>
        /// Holds the commands that are set on the map initialise event in client-side script.
        /// </summary>
        protected StringBuilder InitialisationFunctions
        {
            get
            {
                if (_initialisationFunctions == null)
                {
                    _initialisationFunctions = new StringBuilder();
                }
                return _initialisationFunctions;
            }
        }
        protected StringBuilder _initialisationFunctions;

        /// <summary>
        /// Gets whether the Sensor property is enabled.
        /// </summary>
        public bool Sensor
        {
            get
            {
                return _sensor;
            }
        }
        private bool _sensor;

        /// <summary>
        /// Gets the Licence Key registered for the Google Maps API.
        /// </summary>
        public string Key
        {
            get
            {
                return _key;
            }
        }
        private string _key;

        /// <summary>
        /// Gets/Sets where the temporary files are generated to.
        /// </summary>
        public string TemporaryFolder
        {
            get
            {
                return _temporaryFolder;
            }
            set
            {
                _temporaryFolder = value;
            }
        }
        private string _temporaryFolder;

        /// <summary>
        /// Holds a list of scripts that were run in after the Generate method was 
        /// called and before the state of the control was se to ready.
        /// </summary>
        public Queue<string> ScriptQueue
        {
            get
            {
                if (_scriptQueue == null)
                {
                    _scriptQueue = new Queue<string>();
                }
                return _scriptQueue;
            }
        }
        protected Queue<string> _scriptQueue;


#endregion


#region GWinMap2


        /// <summary>
        /// Sets the map type.
        /// </summary>
        /// <param name="mapType">The map type to set.</param>
        public void SetMapType(GWinMapTypes mapType)
        {
            this.HandleMapCommand("map.setMapType(" + mapType.ToString() + ");");
        }

        /// <summary>
        /// Enables the dragging of the map (enabled by default).
        /// </summary>
        public void EnableDragging()
        {
            this.HandleMapCommand("map.enableDragging();");
        }

        /// <summary>
        /// Disables the dragging of the map.
        /// </summary>
        public void DisableDragging()
        {
            this.HandleMapCommand("map.disableDragging();");
        }

        /// <summary>
        /// Enables info window operations on the map (enabled by default).
        /// </summary>
        public void EnableInfoWindow()
        {
            this.HandleMapCommand("map.enableInfoWindow();");
        }

        /// <summary>
        /// Closes the info window, if it is open, and disables the opening of a new info window.
        /// </summary>
        public void DisableInfoWindow()
        {
            this.HandleMapCommand("map.disableInfoWindow();");
        }

        /// <summary>
        /// Enables double click to zoom in and out (enabled by default).
        /// </summary>
        public void EnableDoubleClickZoom()
        {
            this.HandleMapCommand("map.enableDoubleClickZoom();");
        }

        /// <summary>
        /// Disables double click to zoom in and out.
        /// </summary>
        public void DisableDoubleClickZoom()
        {
            this.HandleMapCommand("map.disableDoubleClickZoom();");
        }

        /// <summary>
        /// Enables continuous smooth zooming for select browsers (disabled by default).
        /// </summary>
        public void EnableContinuousZoom()
        {
            this.HandleMapCommand("map.enableContinuousZoom();");
        }

        /// <summary>
        /// Disables continuous smooth zooming.
        /// </summary>
        public void DisableContinuousZoom()
        {
            this.HandleMapCommand("map.disableContinuousZoom();");
        }

        /// <summary>
        /// Enables the GoogleBar, an integrated search control, to the map. When enabled, this control takes the place of 
        /// the default Powered By Google  logo. Note that this control is not enabled by default. Note: The GoogleBar is 
        /// currently not compatible with the Google Earth plugin, used by map type GMapType.G_SATELLITE_3D_MAP, and will be 
        /// disabled while the Earth plugin is shown.
        /// </summary>
        public void EnableGoogleBar()
        {
            this.HandleMapCommand("map.enableGoogleBar();");
        }

        /// <summary>
        /// Disables the GoogleBar integrated search control. When disabled, the default Powered by Google logo occupies the 
        /// position formerly containing this control. Note that this control is already disabled by default.
        /// </summary>
        public void DisableGoogleBar()
        {
            this.HandleMapCommand("map.disableGoogleBar();");
        }

        /// <summary>
        /// Enables zooming using a mouse's scroll wheel. Note: scroll wheel zoom is disabled by default.
        /// </summary>
        public void EnableScrollWheelZoom()
        {
            this.HandleMapCommand("map.enableScrollWheelZoom();");
        }

        /// <summary>
        /// Disables zooming using a mouse's scroll wheel. Note: scroll wheel zoom is disabled by default.
        /// </summary>
        public void DisableScrollWheelZoom()
        {
            this.HandleMapCommand("map.disableScrollWheelZoom();");
        }

        /// <summary>
        /// Enables pinching to zoom on an iPhone or iPod touch. Note: pinch to zoom is enabled by default.
        /// </summary>
        public void EnablePinchToZoom()
        {
            this.HandleMapCommand("map.enablePinchToZoom();");
        }

        /// <summary>
        /// Disables pinching to zoom on an iPhone or iPod touch. Note: pinch to zoom is enabled by default.
        /// </summary>
        public void DisablePinchToZoom()
        {
            this.HandleMapCommand("map.disablePinchToZoom();");
        }

        /// <summary>
        /// Sets the maps user interface to a default configuration. 
        /// </summary>
        public void SetUIToDefault()
        {
            this.HandleMapCommand("map.setUIToDefault();");
        }



#endregion


#region Modify the Map State


        /// <summary>
        /// Sets the maps center point and zoom level to the zoom point in the GWinMap library.
        /// </summary>
        public void SetCenterToDefault()
        {
            GWinLatLng defaultLatLng = new GWinLatLng(Settings.Default.DefaultCenterLatitude, Settings.Default.DefaultCenterLongitude);
            this.SetCenter(defaultLatLng, Settings.Default.DefaultCenterZoomLevel);
        }

        /// <summary>
        /// Sets the center point without disturbing the current zoom level.
        /// </summary>
        /// <param name="point">The point to center on.</param>
        public void SetCenter(GWinLatLng point)
        {
            this.HandleMapCommand("map.setCenter(new GLatLng(" + point.Latitude.ToString() + ", " + point.Longitude.ToString() + "));");
            _isCenterSet = true;
        }

        /// <summary>
        /// Sets the map view to the given center. Also sets zoom level. 
        /// It is an error to call operations on a newly constructed GMap2 object until after this function is invoked. 
        /// </summary>
        /// <param name="point">The point to center on.</param>
        /// <param name="zoomLevel">The zoom level to set on the center point.</param>
        public void SetCenter(GWinLatLng point, int zoomLevel)
        {
            _isCenterSet = true; 
            this.HandleMapCommand("map.setCenter(new GLatLng(" + point.Latitude.ToString() + ", " + point.Longitude.ToString() + "), " + zoomLevel.ToString() + ");");
        }

        /// <summary>
        /// Sets the map view to center on a postcode and sets zoom level.
        /// </summary>
        /// <param name="postcode">The postcode to geocode and set as center.</param>
        /// <param name="zoomLevel">The zoom level to set on the center point.</param>
        public void SetCenter(string postcode, int zoomLevel)
        {
            throw new NotImplementedException("Need to find a nice way to add a callback function.");
            //string script = "var geocoder = new GClientGeocoder();";
        }

        /// <summary>
        /// Changes the center point of the map to the given point. If the point is already visible 
        /// in the current map view, change the center in a smooth animation.
        /// </summary>
        /// <param name="point">The point to pan to.</param>
        public void PanTo(GWinLatLng point)
        {
            this.HandleMapCommand("map.panTo(new GLatLng(" + point.Latitude.ToString() + ", " + point.Longitude.ToString() + "));");
        }

        /// <summary>
        /// Starts a pan animation by the given distance in pixels.
        /// </summary>
        /// <param name="size">The GWinSize that holds the distances (in pixels) to pan by.</param>
        public void PanBy(GWinSize size)
        {
            this.HandleMapCommand("map.PanBy(new GSize(" + size.Width.ToString() + "," + size.Height.ToString() + "));");
        }

        /// <summary>
        /// Starts a pan animation by half the width of the map in the indicated directions.
        /// </summary>
        /// <param name="x">+1 is right, -1 is left, 0 is no change.</param>
        /// <param name="x">+1 is down, -1 is up, 0 is no change.</param>
        /// <param name="y"></param>
        public void PanDirection(int x, int y)
        {
            this.HandleMapCommand("map.panDirection(" + x.ToString() + "," + y.ToString() + ");");
        }


#endregion


#region Overlays


        /// <summary>
        /// Removes all overlays from the map, and fires the clearoverlays event.
        /// </summary>
        public void ClearOverlays()
        {
            this.HandleMapCommand("map.clearOverlays();");
        }

        /// <summary>
        /// Adds a marker to the map.
        /// </summary>
        /// <param name="latLng">The latitude and longitude at which to place the marker.</param>
        public void AddMarker(GWinLatLng latLng)
        {
            this.AddMarker(latLng, false);
        }

        /// <summary>
        /// Adds a marker to the map and allows the option of centering the map on the marker after it's added.
        /// </summary>
        /// <param name="latLng">The latitude and longitude at which to place the marker.</param>
        /// <param name="center">Whether to center on the marker.</param>
        public void AddMarker(GWinLatLng latLng, bool center)
        {

            // Add the marker
            string markerScript = "var point = new GLatLng(" + latLng.Latitude.ToString() + "," + latLng.Longitude.ToString() + ");" +
                "map.addOverlay(new GMarker(point));";

            this.HandleMapCommand(markerScript);

            // Center on the marker if required
            if (center)
            {
                this.SetCenter(latLng);
            }

        }


#endregion


#region Rotation


        /// <summary>
        /// Enables rotation on the map. If the map's current GMapType  has rotatable aerial imagery in 
        /// the current viewport, the aerial imagery will be automatically displayed on the map. Aerial 
        /// imagery is only available for G_SATELLITE_MAP and G_HYBRID_MAP. 
        /// </summary>
        public void EnableRotation()
        {
            this.HandleMapCommand("map.enableRotation();");
        }

        /// <summary>
        /// Disables rotation on the map. If rotatable aerial imagery is currently being shown on the map, 
        /// top-down imagery will be automatically displayed on the map instead. 
        /// </summary>
        public void DisableRotation()
        {
            this.HandleMapCommand("map.disableRotation();");
        }


#endregion


#region Calculations


        /// <summary>
        /// Calculates the center point of the provided longitudes and latitudes.
        /// </summary>
        /// <param name="positions">The positions to find the center point for.</param>
        /// <returns>The central point between all the provided longs and lats.</returns>
        public GWinLatLng CalculateCenterPoint(GWinLatLng[] positions)
        {
            return new GWinLatLng();
        }


#endregion


#region Helpers


        /// <summary>
        /// Handles the script to be actioned in the WebBrowser.
        /// </summary>
        /// <param name="script">The script to handle.</param>
        protected void HandleMapCommand(string script)
        {
            if (this.IsCenterSet)
            {
                if (this.IsReady)
                {
                    this.InjectFunction(script);
                }
                else if (this.IsGenerated)
                {
                    this.ScriptQueue.Enqueue(script);
                }
                else
                {
                    this.InitialisationFunctions.AppendLine(script);
                }
            }
            else
            {
                throw new GWinMapException(GWinMapErrors.CENTER_POINT_NOT_SET);
            }    
        }

        /// <summary>
        /// Injects the specified script into the WebBrowser.
        /// </summary>
        /// <param name="script">The script to inject into the Document.</param>
        protected void InjectFunction(string script)
        {

            if (this.ReadyState == WebBrowserReadyState.Complete)
            {

                // Create a new script element
                HtmlElement head = this.Document.GetElementsByTagName("head")[0];
                HtmlElement scriptBlock = this.Document.CreateElement("script");
                IHTMLScriptElement element = (IHTMLScriptElement)scriptBlock.DomElement;

                // Insert and invoke the function
                string functionName = "Action";
                element.text = "function " + functionName + "() { " + script + " }";
                head.AppendChild(scriptBlock);
                this.Document.InvokeScript(functionName);

            }
            else
            {
                throw new GWinMapException(GWinMapErrors.CONTROL_NOT_READY);
            }

        }

        /// <summary>
        /// Clears the map data. After this method is called, the Generation must be called again.
        /// </summary>
        public void Clear()
        {
            _isReady = false;
            this.Navigate("about:blank");
        }


#endregion


#region Internal Events


        void GWinMap_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
        {

            // Mark the control as ready and run in any waiting scripts.
            _isReady = true;

            // Run in the queued scripts
            while (this.ScriptQueue.Count > 0)
            {
                this.HandleMapCommand(this.ScriptQueue.Dequeue());
            }            

        }


#endregion


#region Clean Up

        
        /// <summary>
        /// Overrides the dispose method to delete the generated file.
        /// </summary>
        protected override void Dispose(bool disposing)
        {
            File.Delete(this.GeneratedFilePath);
            base.Dispose(disposing);
        }


#endregion


    }
}
